﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Media3D;
using System.Windows.Media.Animation;
using System.Diagnostics;

namespace GG.Base
{
    /// <summary>
    /// Interaction logic for IsoGrid.xaml
    /// </summary>
    public partial class IsoGridView : UserControl
    {
        private Viewport3D viewport;
        public IsoCameraControl cameraControl;

        private ModelVisual3D lightVisual;
        private Viewport2DVisual3D gridVisual;

        private Rectangle rect;
        private DrawingBrush brush;
        private DrawingGroup group;

        private DrawingGroup lowerLayer;
        private DrawingGroup middleLayer;
        private DrawingGroup upperLayer;


        private RotateTransform3D gridRotation;
        private TranslateTransform3D gridTranslation;

        private List<IGridDrawable> gridObjects;
        public List<IGridDrawable> GridObjects { get { return gridObjects; } }


        private Orientation lookOrientation;
        public Orientation LookOrientation { get { return lookOrientation; } set { gridRotation.Rotation = new AxisAngleRotation3D(new Vector3D(0, 0, 1), (int)value); lookOrientation = (Orientation)PositionUtils.Wrap((int)value, 360); } }

        public readonly int BLOCKSIZE = 1024;
        public double ZOOMINCREMENT = 0.25;

        public int TurnTime = 1;

        public static Vector3D UP = new Vector3D(0, 0, 1);
        private static double toRadians = Math.PI / 180;

        public IsoGridView()
        {
            this.InitializeComponent();


            //Setup viewport and cameracontrol
            viewport = new Viewport3D();
            cameraControl = new IsoCameraControl();
            viewport.Camera = cameraControl.Camera;
            viewport.ClipToBounds = true;

            //Create ambient light
            lightVisual = new ModelVisual3D();
            AmbientLight light = new AmbientLight(Colors.White);
            lightVisual.Content = light;


            RenderOptions.SetEdgeMode(this, EdgeMode.Aliased);
            SnapsToDevicePixels = true;


            //Setup viewport and create grid.
            SetupViewport();
            GridRadius = 2;

            Pan = new Point(0, 0);

            //Add Event listeners
            MouseLeftButtonDown += new MouseButtonEventHandler(MouseLeftButtonDownHandler);
            MouseLeftButtonUp += new MouseButtonEventHandler(MouseLeftButtonUpHandler);
            MouseWheel += new MouseWheelEventHandler(MouseWheelHandler);
            MouseMove += new MouseEventHandler(MouseMoveHandler);

            //Add visuals to viewport
            viewport.Children.Add(gridVisual);
            viewport.Children.Add(lightVisual);

            gridObjects = new List<IGridDrawable>();


            RenderOptions.SetEdgeMode(viewport, EdgeMode.Aliased);

            this.Content = viewport;
        }

        public Point Pan
        {
            set
            {
                gridTranslation.OffsetX = PositionUtils.Crop(-value.X, -gridRadius, gridRadius);
                gridTranslation.OffsetY = PositionUtils.Crop(-value.Y, -gridRadius, gridRadius);
            }
            get
            {
                return new Point(gridTranslation.OffsetX, gridTranslation.OffsetY);
            }
        }

        Point start;
        void MouseLeftButtonUpHandler(object sender, MouseButtonEventArgs e)
        {
            ReleaseMouseCapture();
        }

        void MouseLeftButtonDownHandler(object sender, MouseButtonEventArgs e)
        {
            start = e.GetPosition(this);
            lastPan = Pan;
            CaptureMouse();
        }
        Point lastPan;
        void MouseMoveHandler(object sender, MouseEventArgs e)
        {
            if (!IsMouseCaptured || inAnimation) return;
            Point delta = (start - (Vector)e.GetPosition(this));
            Pan = new Point(-lastPan.X + (delta.X * Math.Cos((int)lookOrientation * toRadians) - delta.Y * Math.Sin((int)lookOrientation * toRadians)) / cameraControl.Zoom / 1000, -lastPan.Y + (-delta.Y * Math.Cos((int)lookOrientation * toRadians) - delta.X * Math.Sin((int)lookOrientation * toRadians)) / cameraControl.Zoom / 1000);
            //System.Diagnostics.Debug.WriteLine("delta: " + delta + ", pan: " + Pan);
        }

        void MouseWheelHandler(object sender, MouseWheelEventArgs e)
        {
            cameraControl.Zoom += (e.Delta > 0) ? ZOOMINCREMENT : -ZOOMINCREMENT;
        }

        private void SetupViewport()
        {
            gridVisual = new Viewport2DVisual3D();
            gridVisual.CacheMode = new BitmapCache(2048);
            (gridVisual.CacheMode as BitmapCache).RenderAtScale = 8192;


            rect = new Rectangle();
            rect.SnapsToDevicePixels = true;
            RenderOptions.SetEdgeMode(gridVisual, EdgeMode.Aliased);
            RenderOptions.SetEdgeMode(rect, EdgeMode.Aliased);
            rect.Stretch = Stretch.Fill;

            gridVisual.Visual = rect;
            brush = new DrawingBrush();
            brush.Viewport = new Rect(0, 0, 1, 1);
            brush.TileMode = TileMode.None;
            brush.AlignmentX = AlignmentX.Center;
            brush.AlignmentY = AlignmentY.Center;
            brush.Stretch = Stretch.None;
            rect.Fill = brush;
            RenderOptions.SetEdgeMode(brush, EdgeMode.Aliased);

            group = new DrawingGroup();
            brush.Drawing = group;

            RenderOptions.SetEdgeMode(group, EdgeMode.Aliased);
            lowerLayer = new DrawingGroup();
            group.Children.Add(lowerLayer);

            middleLayer = new DrawingGroup();
            group.Children.Add(middleLayer);
            RenderOptions.SetEdgeMode(middleLayer, EdgeMode.Aliased);


            upperLayer = new DrawingGroup();
            group.Children.Add(upperLayer);

            DiffuseMaterial myMaterial = new DiffuseMaterial();
            Viewport2DVisual3D.SetIsVisualHostMaterial(myMaterial, true);
            gridVisual.Material = myMaterial;


            //Rotation and Translation
            gridRotation = new RotateTransform3D();
            gridRotation.Rotation = new AxisAngleRotation3D(UP, 0);

            gridTranslation = new TranslateTransform3D();

            gridVisual.Transform = new Transform3DGroup();
            (gridVisual.Transform as Transform3DGroup).Children.Add(gridTranslation);
            (gridVisual.Transform as Transform3DGroup).Children.Add(gridRotation);

            LookOrientation = Orientation.SouthWest;
        }

        private void UpdateGrid()
        {
            //Set visualGrid geometry
            MeshGeometry3D mesh = new MeshGeometry3D();


            //Create mesh
            // Create a collection of vertex positions for the MeshGeometry3D. 
            Point3DCollection positionCollection = new Point3DCollection();
            positionCollection.Add(new Point3D(-gridRadius, -gridRadius, 0));
            positionCollection.Add(new Point3D(gridRadius, -gridRadius, 0));
            positionCollection.Add(new Point3D(gridRadius, gridRadius, 0));
            positionCollection.Add(new Point3D(-gridRadius, -gridRadius, 0));
            positionCollection.Add(new Point3D(gridRadius, gridRadius, 0));
            positionCollection.Add(new Point3D(-gridRadius, gridRadius, 0));
            mesh.Positions = positionCollection;

            // Create a collection of texture coordinates for the MeshGeometry3D.
            PointCollection textureCoordinatesCollection = new PointCollection();
            textureCoordinatesCollection.Add(new Point(0, 0));
            textureCoordinatesCollection.Add(new Point(1, 0));
            textureCoordinatesCollection.Add(new Point(1, 1));
            textureCoordinatesCollection.Add(new Point(0, 0));
            textureCoordinatesCollection.Add(new Point(1, 1));
            textureCoordinatesCollection.Add(new Point(0, 1));
            mesh.TextureCoordinates = textureCoordinatesCollection;

            // Create a collection of triangle indices for the MeshGeometry3D.
            Int32Collection triangleIndicesCollection = new Int32Collection();
            triangleIndicesCollection.Add(0);
            triangleIndicesCollection.Add(1);
            triangleIndicesCollection.Add(2);
            triangleIndicesCollection.Add(3);
            triangleIndicesCollection.Add(4);
            triangleIndicesCollection.Add(5);
            mesh.TriangleIndices = triangleIndicesCollection;

            gridVisual.Geometry = mesh;

            //every block has a width and height of 1024 in texture space and 1 in 3d space.
            rect.Height = rect.Width = 2 * BLOCKSIZE * gridRadius;
            upperLayer.Transform = middleLayer.Transform = new TranslateTransform(BLOCKSIZE * gridRadius, BLOCKSIZE * gridRadius);
            lowerLayer.Children.Clear();

            //lowerLayer.Children.Add(new GeometryDrawing(new SolidColorBrush(Colors.White), new Pen(), new RectangleGeometry(new Rect(0, 0, 4 * BLOCKSIZE, 4 * BLOCKSIZE))));
            lowerLayer.Children.Add(new ImageDrawing(new BitmapImage(new Uri("Test.png", UriKind.Relative)), new Rect(0, 0, 4 * BLOCKSIZE, 4 * BLOCKSIZE)));//TEMP
            
        }

        public void TurnLeft()
        {
            Orientation o = lookOrientation;
            o += 90;
            o = (Orientation)PositionUtils.Wrap((int)o, 360);
            TurnToOrientation(o);
        }

        public void TurnRight()
        {
            Orientation o = lookOrientation;
            o -= 90;
            o = (Orientation)PositionUtils.Wrap((int)o, 360);
            TurnToOrientation(o);
        }

        public void GoTopDown(Orientation up)
        {
            if (topDown) return;
            topDown = true;
            cameraControl.GoToInclination(90);
            TurnToOrientation(up);
        }

        public void GoIso()
        {
            if (!topDown) return;
            topDown = false;
            cameraControl.GoToInclination(45);
            TurnToOrientation(lookOrientation - 45);
        }

        Boolean topDown = false;

        public void SwitchTopDown(Orientation up)
        {
            if (topDown)
            {
                GoIso();
            }
            else
            {
                GoTopDown(up);
            }
        }

        private Boolean inAnimation = false;

        public void TurnToOrientation(Orientation o)
        {
            if (lookOrientation == o) return;
            Rotation3DAnimation anim = new Rotation3DAnimation();
            anim.From = gridRotation.Rotation;
            anim.To = new AxisAngleRotation3D(UP, (int)o);
            anim.Duration = new System.Windows.Duration(TimeSpan.FromSeconds(TurnTime));
            anim.AutoReverse = false;
            anim.Completed += new EventHandler(anim_Completed);

            gridRotation.BeginAnimation(RotateTransform3D.RotationProperty, anim);
            lookOrientation = o;
            inAnimation = true;
        }

        void anim_Completed(object sender, EventArgs e)
        {
            inAnimation = false;
        }

        private int gridRadius;
        public int GridRadius
        {
            get
            {
                return gridRadius;
            }
            set
            {
                if (gridRadius == value) return;
                gridRadius = value;
                UpdateGrid();
            }
        }

        public void ZoomToPoint(Point point)
        {
            GoTopDown(lookOrientation + 45);
            cameraControl.GoToZoomLevel(1);
        }


        public void AddGridDrawable(IGridDrawable go)
        {
            if (gridObjects.IndexOf(go) >= 0) return;
            gridObjects.Add(go);
            middleLayer.Children.Add(go.Drawing);
        }

        public void RemoveGridDrawable(IGridDrawable go)
        {
            if (gridObjects.IndexOf(go) >= 0) return;
            gridObjects.Remove(go);
            middleLayer.Children.Remove(go.Drawing);
        }

    }
}